﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LoU.Dungeons
{
    public class Compute
    {
        const double dmg = 100000;
        public static IEnumerable<RatioGain> GainPerTS(
            UnitType u1, int researchU1,
            UnitType u2, int researchU2,
            double roundtripTime, DungeonType dungeonType, double CCUsage,
            MilitaryBuild forcedBuild)
        {
            if (Data.Units[u1].AttackType != Data.Units[u2].AttackType)
            {
                throw new NotImplementedException("Program doesn't support mixing troops with different attack types.");
            }
            List<RatioGain> result = new List<RatioGain>();
            if (u1 == u2 && researchU1 == researchU2)
            {
                result.Add(Gain(dungeonType, CCUsage, forcedBuild, roundtripTime, 1.0, u1, u2, researchU1, researchU2));
            }
            else
            {
                for (double TSratioOfU1 = 1.0; TSratioOfU1 >= 0.0; TSratioOfU1 = Math.Round(TSratioOfU1 - 0.01, 2))
                {
                    result.Add(Gain(dungeonType, CCUsage, forcedBuild, roundtripTime, TSratioOfU1, u1, u2, researchU1, researchU2));
                }
            }
            return result;
        }

        private static RatioGain Gain(DungeonType dungeonType, double CCUsage, MilitaryBuild forceBuild, double roundtripTime,
            double TSratioOfU1, UnitType u1, UnitType u2, int researchU1, int researchU2)
        {
            int u1Attack = (int)Math.Round(Data.Units[u1].Attack * (1 + (double)researchU1 / 100.0), 0);
            int u2Attack = (int)Math.Round(Data.Units[u2].Attack * (1 + (double)researchU2 / 100.0), 0);

            double TSratioOfU2 = 1.0 - TSratioOfU1;
            double averageCC = (double)Data.Units[u1].CC * TSratioOfU1 + (double)Data.Units[u2].CC * TSratioOfU2;
            double AtkRatioOfU1 = (u1Attack * TSratioOfU1) / (u1Attack * TSratioOfU1 + u2Attack * TSratioOfU2);

            Resources loot = LootRepartition(dungeonType, dmg * LootPerDamage(dungeonType, u1));

            double loss1 = dmg * AtkRatioOfU1 / (double)u1Attack;
            double loss2 = dmg * (1 - AtkRatioOfU1) / (double)u2Attack;

            double TS = loot.TotalWithoutGold / averageCC + loss1 + loss2;
            double TS1 = TS * TSratioOfU1;
            double TS2 = TS * TSratioOfU2;

            Resources costsPerTS = new Resources
            {
                // CC usage is not applied on food cost per TS because extra sent troops must also eat
                { ResourceType.Food, (Data.Units[u1].DailyFoodCost * TSratioOfU1 + Data.Units[u2].DailyFoodCost * TSratioOfU2) / 24.0 * roundtripTime },
                { ResourceType.Gold, (Data.Units[u1].RecruitCost[ResourceType.Gold] * loss1 + Data.Units[u2].RecruitCost[ResourceType.Gold] * loss2) / TS * CCUsage },
                { ResourceType.Iron, (Data.Units[u1].RecruitCost[ResourceType.Iron] * loss1 + Data.Units[u2].RecruitCost[ResourceType.Iron] * loss2) / TS * CCUsage },
                { ResourceType.Stone, (Data.Units[u1].RecruitCost[ResourceType.Stone] * loss1 + Data.Units[u2].RecruitCost[ResourceType.Stone] * loss2) / TS * CCUsage },
                { ResourceType.Wood, (Data.Units[u1].RecruitCost[ResourceType.Wood] * loss1 + Data.Units[u2].RecruitCost[ResourceType.Wood] * loss2) / TS * CCUsage },
            };

            Resources balancePerTS = new Resources
            {
                { ResourceType.Food, loot[ResourceType.Food] / TS * CCUsage - costsPerTS[ResourceType.Food] },
                { ResourceType.Gold, loot[ResourceType.Gold] / TS * CCUsage - costsPerTS[ResourceType.Gold] },
                { ResourceType.Iron, loot[ResourceType.Iron] / TS * CCUsage - costsPerTS[ResourceType.Iron] },
                { ResourceType.Stone, loot[ResourceType.Stone] / TS * CCUsage - costsPerTS[ResourceType.Stone] },
                { ResourceType.Wood, loot[ResourceType.Wood] / TS * CCUsage - costsPerTS[ResourceType.Wood] },
            };

            // build optimization
            double gainPerHour = double.MinValue;
            MilitaryBuild optimalBuild = null;
            double maxReplacableTSWithinRountripTime = 0;
            double rs1 = 0;
            double rs2 = 0;
            if (forceBuild != null)
            {
                gainPerHour = GainPerHourForBuild(forceBuild, balancePerTS.Total, u1, loss1, u2, loss2, TS, roundtripTime, out maxReplacableTSWithinRountripTime);
                optimalBuild = forceBuild;
                rs1 = forceBuild.RecruitSpeed[u1];
                rs2 = forceBuild.RecruitSpeed[u2];
            }
            else
            {
                foreach (MilitaryBuild build in Data.CastleBuilds.Where(b => b.AvailableUnits.Contains(u1) || b.AvailableUnits.Contains(u2)))
                {
                    double mrtswrtt;
                    double g = GainPerHourForBuild(build, balancePerTS.Total, u1, loss1, u2, loss2, TS, roundtripTime, out mrtswrtt);
                    if (g > gainPerHour)
                    {
                        gainPerHour = g;
                        optimalBuild = build;
                        maxReplacableTSWithinRountripTime = mrtswrtt;
                    }
                }
            }

            return new RatioGain
            {
                Ratio = (int)(TSratioOfU1 * 100.0),
                Balance = balancePerTS,
                Build = optimalBuild,
                RoundTripTime = roundtripTime,
                MaxReplacableTSWithinRountripTime = maxReplacableTSWithinRountripTime,
                CCUsage = CCUsage,
            };
        }

        private static double GainPerHourForBuild(MilitaryBuild build, double gainPerTS,
            UnitType u1, double loss1, UnitType u2, double loss2,
            double TS, double roundtripTime, out double maxReplacableTSWithinRountripTime)
        {
            double replacementTime = (loss1 * build.RecruitSpeed[u1] + loss2 * build.RecruitSpeed[u2]) / 3600.0;
            double replacementTimePerTS = replacementTime / TS;
            maxReplacableTSWithinRountripTime = roundtripTime / replacementTimePerTS;
            return gainPerTS * Math.Min((double)build.MaxTS, maxReplacableTSWithinRountripTime) / roundtripTime;
        }

        private static double LootPerDamage(DungeonType dungeon, UnitType unit)
        {
            return Data.Vulnerabilities[dungeon] == Data.Units[unit].AttackType ?
                Data.LootPerDamageHigh[dungeon] : Data.LootPerDamage[dungeon];
        }

        private static Resources LootRepartition(DungeonType dungeon, double totalWithoutGold)
        {
            return new Resources
            {
                { ResourceType.Food, totalWithoutGold * Data.ResourcesRepartition[dungeon][ResourceType.Food] },
                { ResourceType.Gold, totalWithoutGold * Data.ResourcesRepartition[dungeon][ResourceType.Gold] },
                { ResourceType.Iron, totalWithoutGold * Data.ResourcesRepartition[dungeon][ResourceType.Iron] },
                { ResourceType.Stone, totalWithoutGold * Data.ResourcesRepartition[dungeon][ResourceType.Stone] },
                { ResourceType.Wood, totalWithoutGold * Data.ResourcesRepartition[dungeon][ResourceType.Wood] },
            };
        }
    }
}
